package org.jeecg.modules.system.aspect;

import java.lang.reflect.Method;
import java.util.Properties;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.jeecg.common.util.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.PropertyPlaceholderHelper;

import com.alibaba.fastjson.JSON;
import com.bigdata.common.crm.annotation.AutoCache;
import com.bigdata.common.crm.annotation.consts.CacheType;
import com.bigdata.common.crm.consts.CacheKeyConsts;
import com.bigdata.common.crm.consts.CommonConsts;

import lombok.extern.slf4j.Slf4j;

/**
 * 自动缓存切面
 *
 * @author nico
 * @date 2020年1月19日
 */
@Aspect
@Component
@Slf4j
public class AutoCacheAspect {

	@Autowired
	private RedisUtil redisUtil;

	private static PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper(
			"${", "}", ".", false);

	@Pointcut("@annotation(com.bigdata.common.crm.annotation.AutoCache)")
	public void pointCut() {

	}

	@Around("pointCut()")
	public Object arround(ProceedingJoinPoint point) throws Throwable{
		MethodSignature signature = (MethodSignature) point.getSignature();
		Method method = signature.getMethod();

		String[] methodArgNames = signature.getParameterNames();
		Object[] methodArgs = point.getArgs();
		Properties methodArgNameMap = new Properties();
		if(! ArrayUtils.isEmpty(methodArgNames)) {
			for(int index = 0; index < methodArgNames.length; index ++) {
				methodArgNameMap.put(methodArgNames[index], methodArgs[index]);
			}
		}

		AutoCache autoCache = method.getAnnotation(AutoCache.class);
		String cacheKey = autoCache.value();
		String hashKey = autoCache.hashKey();
		long expire = autoCache.expire();
		long expireShakeRange = autoCache.expire();
		if (expire > 0 && expireShakeRange > 0 && expireShakeRange < expire) {
			expire = expire - (long)(Math.random() * expireShakeRange);	
		}
		
		CacheType cacheType = autoCache.type();

		cacheKey = helper.replacePlaceholders(cacheKey, methodArgNameMap);
		log.info("AutoCache 获取到key {}", cacheKey);

		switch (cacheType){
		case STRING:
			return handleStringCache(point, method, cacheKey, expire);
		case HASH:
			if (! StringUtils.isEmpty(hashKey)){
				hashKey = helper.replacePlaceholders(hashKey, methodArgNameMap);	
				return handleHashCache(point, method, cacheKey, hashKey, expire);
			}
		}

		return  point.proceed();
	}

	public Object handleStringCache(ProceedingJoinPoint point, Method method, String cacheKey, long expire) throws Throwable{
		Object result = null;
		String cacheValue = (String) redisUtil.get(cacheKey);
		if(StringUtils.isEmpty(cacheValue)) {
			boolean lockSuc = false;
			String lockKey = CacheKeyConsts.CACHE_LOCK_PREFIX + cacheKey;
			try {
				lockSuc = redisUtil.lock(lockKey, cacheValue);
				cacheValue = (String) redisUtil.get(cacheKey);
				if(StringUtils.isEmpty(cacheValue)) {
					result = point.proceed();
					cacheValue = result == null ? CommonConsts.NULL : JSON.toJSONString(result);
					
					boolean suc = redisUtil.set(cacheKey, cacheValue);
					if (expire > 0){
						redisUtil.expire(cacheKey, expire);
					}
					log.info("写缓存 key {}, value {} 的状态为{}", cacheKey, cacheValue, suc);
				}
			}finally {
				if (lockSuc){
					redisUtil.unlock(lockKey, cacheValue);
				}
			}
		}
		if(! cacheValue.equalsIgnoreCase(CommonConsts.NULL)){
			result = JSON.parseObject(cacheValue, method.getReturnType());
		}
		return result;
	}

	public Object handleHashCache(ProceedingJoinPoint point, Method method, String cacheKey, String hashKey, long expire) throws Throwable{
		Object result = null;
		String cacheValue = (String) redisUtil.hget(cacheKey, hashKey);
		if(StringUtils.isEmpty(cacheValue)) {
			boolean lockSuc = false;
			String lockKey = CacheKeyConsts.CACHE_LOCK_PREFIX + cacheKey + hashKey;
			try {
				lockSuc = redisUtil.lock(lockKey, cacheValue);
				cacheValue = (String) redisUtil.hget(cacheKey, hashKey);
				if(StringUtils.isEmpty(cacheValue)) {
					result = point.proceed();
					cacheValue = result == null ? CommonConsts.NULL : JSON.toJSONString(result);
					
					boolean suc = redisUtil.hset(cacheKey, hashKey, cacheValue);
					if (expire > 0){
						redisUtil.expire(cacheKey, expire);
					}
					log.info("写缓存 key {}, hashKey {}, value {} 的状态为{}", cacheKey, hashKey, cacheValue, suc);
				}
			}finally {
				if (lockSuc){
					redisUtil.unlock(lockKey, cacheValue);
				}
			}
		}
		if(! cacheValue.equalsIgnoreCase(CommonConsts.NULL)){
			result = JSON.parseObject(cacheValue, method.getReturnType());
		}
		return result;
	}
}
